package com.surfacing.rcp.core.net;

import java.util.ArrayList;
import java.util.List;


import org.apache.log4j.Logger;
import org.eclipse.core.runtime.Plugin;
import org.osgi.framework.BundleContext;

import com.surfacing.rcp.core.net.event.INetListener;
import com.surfacing.rcp.core.net.event.NetEvent;
import com.surfacing.rcp.core.net.internal.ClientSession;
import com.surfacing.rcp.core.net.internal.MyClient;
import com.surfacing.rcp.core.net.internal.ServerInfo;
import com.surfacing.rcp.model.QueryFilter;
import com.surfacing.rcp.model.codec.AnforderungDecoder;
import com.surfacing.rcp.model.codec.AnforderungEncoder;
import com.surfacing.rcp.model.codec.DienststelleDecoder;
import com.surfacing.rcp.model.codec.DienststelleEncoder;
import com.surfacing.rcp.model.codec.DoctorDecoder;
import com.surfacing.rcp.model.codec.DoctorEncoder;
import com.surfacing.rcp.model.codec.EinsatzartDecoder;
import com.surfacing.rcp.model.codec.EinsatzartEncoder;
import com.surfacing.rcp.model.codec.JournalDecoder;
import com.surfacing.rcp.model.codec.JournalEncoder;
import com.surfacing.rcp.model.codec.LoginDecoder;
import com.surfacing.rcp.model.codec.LoginEncoder;
import com.surfacing.rcp.model.codec.LogoutDecoder;
import com.surfacing.rcp.model.codec.LogoutEncoder;
import com.surfacing.rcp.model.codec.NacaDecoder;
import com.surfacing.rcp.model.codec.NacaEncoder;
import com.surfacing.rcp.model.codec.NfsDecoder;
import com.surfacing.rcp.model.codec.NfsEncoder;
import com.surfacing.rcp.model.codec.ProtokollNrDecoder;
import com.surfacing.rcp.model.codec.ProtokollNrEncoder;
import com.surfacing.rcp.model.codec.StatAnforderungDecoder;
import com.surfacing.rcp.model.codec.StatAnforderungEncoder;
import com.surfacing.rcp.model.codec.StatDienststelleDecoder;
import com.surfacing.rcp.model.codec.StatDienststelleEncoder;
import com.surfacing.rcp.model.codec.StatDoctorDecoder;
import com.surfacing.rcp.model.codec.StatDoctorEncoder;
import com.surfacing.rcp.model.codec.StatEinsatzartDecoder;
import com.surfacing.rcp.model.codec.StatEinsatzartEncoder;
import com.surfacing.rcp.model.codec.StatNacaDecoder;
import com.surfacing.rcp.model.codec.StatNacaEncoder;
import com.surfacing.rcp.model.codec.StatNfsDecoder;
import com.surfacing.rcp.model.codec.StatNfsEncoder;
import com.surfacing.rcp.model.codec.StatTransportzielDecoder;
import com.surfacing.rcp.model.codec.StatTransportzielEncoder;
import com.surfacing.rcp.model.codec.SystemMessageDecoder;
import com.surfacing.rcp.model.codec.SystemMessageEncoder;
import com.surfacing.rcp.model.codec.TransportzielDecoder;
import com.surfacing.rcp.model.codec.TransportzielEncoder;
import com.surfacing.rcp.model.codec.UserDecoder;
import com.surfacing.rcp.model.codec.UserEncoder;
import com.surfacing.rcp.model.common.AbstractMessage;
import com.surfacing.rcp.model.common.IConnectionStates;
import com.surfacing.rcp.model.common.IModelActions;
import com.surfacing.rcp.model.common.IModelListener;
import com.surfacing.rcp.model.entity.Anforderung;
import com.surfacing.rcp.model.entity.Dienststelle;
import com.surfacing.rcp.model.entity.Doctor;
import com.surfacing.rcp.model.entity.Einsatzart;
import com.surfacing.rcp.model.entity.Journal;
import com.surfacing.rcp.model.entity.Login;
import com.surfacing.rcp.model.entity.Logout;
import com.surfacing.rcp.model.entity.Naca;
import com.surfacing.rcp.model.entity.Nfs;
import com.surfacing.rcp.model.entity.ProtokollNr;
import com.surfacing.rcp.model.entity.StatAnforderung;
import com.surfacing.rcp.model.entity.StatDienststelle;
import com.surfacing.rcp.model.entity.StatDoctor;
import com.surfacing.rcp.model.entity.StatEinsatzart;
import com.surfacing.rcp.model.entity.StatNaca;
import com.surfacing.rcp.model.entity.StatNfs;
import com.surfacing.rcp.model.entity.StatTransportziel;
import com.surfacing.rcp.model.entity.SystemMessage;
import com.surfacing.rcp.model.entity.Transportziel;
import com.surfacing.rcp.model.entity.User;
import com.surfacing.rcp.model.factory.ListenerFactory;
import com.surfacing.rcp.model.factory.ProtocolCodecFactory;
import com.surfacing.rcp.model.factory.XMLFactory;

/**
 * The activator class controls the plug-in life cycle
 */
public class NetWrapper extends Plugin implements INetListener {
	private static final Logger log = Logger.getLogger(NetWrapper.class);
	// The plug-in ID
	public static final String PLUGIN_ID = "at.rc.tacos.core.net";
	// The shared instance
	private static NetWrapper plugin;

	// properties
	private ClientSession clientSession;
	private ServerInfo connectionInfo;
	private boolean connected;

	/**
	 * The constructor
	 */
	public NetWrapper() {
	}

	/**
	 * Called when the plugin is started
	 * 
	 * @param context
	 *            lifecyle informations
	 * @throws Exception
	 *             when a error occures during startup
	 */
	@Override
	public void start(BundleContext context) throws Exception {
		super.start(context);
		plugin = this;
	}

	/**
	 * Called when the plugin is stopped
	 * 
	 * @param context
	 *            lifecyle informations
	 * @throws Exception
	 *             when a error occures during shutdown
	 */
	@Override
	public void stop(BundleContext context) throws Exception {
		super.stop(context);
		plugin = null;
	}

	/**
	 * Returns the shared instance
	 * 
	 * @return the shared instance
	 */
	public static NetWrapper getDefault() {
		if (plugin == null)
			plugin = new NetWrapper();
		return plugin;
	}

	/**
	 * Opens a connection to the given server.
	 * 
	 * @param connectionName
	 *            the name of the server to connect
	 */
	public void connectNetwork(String connectionName) {
		// get the server
		connectionInfo = NetSource.getInstance().getServerInfoById(
				connectionName);
		if (connectionInfo == null) {
			log.error("Cannot resolve server info: " + connectionName);
			// reset the info
			connected = false;
			return;
		}
		log.info("Connection to: " + connectionInfo.getHostName() + ":"
				+ connectionInfo.getPort());
		MyClient connection = NetSource.getInstance().getConnection(
				connectionInfo);
		// check the status
		if (connection != null) {
			// add this class as target for network messages
			connection.addNetListener(this);
			// create a new client session
			clientSession = new ClientSession(connection);
			// we have a connection
			connected = true;
		} else {
			// not connected
			connected = false;
			// destroy the session
			clientSession = null;
			// destory the server info
			connectionInfo = null;
		}
	}

	/**
	 * Returns current client session used for the nework communication
	 * 
	 * @param the
	 *            client session
	 */
	public ClientSession getClientSession() {
		return clientSession;
	}

	/**
	 * Returns the current status of the network connection
	 * 
	 * @return true if the network connection is established
	 */
	public boolean isConnected() {
		return connected;
	}

	/**
	 * Closes the current connection and cleanup.
	 */
	public void closeConnection() {
		// get the client session
		if (clientSession == null)
			return;
		// get the connection
		MyClient connection = clientSession.getConnection();
		// assert we have a connection
		if (connection == null)
			return;
		// close
		NetSource.getInstance().closeConnection(connection);
	}

	/**
	 * Convenience method to registers the encoders and decoders.
	 */
	public void registerEncoderAndDecoder() {
		// register the needed model types with the decoders and encoders
		ProtocolCodecFactory protFactory = ProtocolCodecFactory.getDefault();
		protFactory.registerEncoder(Anforderung.ID, new AnforderungEncoder());
		protFactory.registerDecoder(Anforderung.ID, new AnforderungDecoder());
		protFactory.registerEncoder(Dienststelle.ID, new DienststelleEncoder());
		protFactory.registerDecoder(Dienststelle.ID, new DienststelleDecoder());
		protFactory.registerEncoder(Doctor.ID, new DoctorEncoder());
		protFactory.registerDecoder(Doctor.ID, new DoctorDecoder());
		protFactory.registerEncoder(Einsatzart.ID, new EinsatzartEncoder());
		protFactory.registerDecoder(Einsatzart.ID, new EinsatzartDecoder());
		protFactory.registerEncoder(Journal.ID, new JournalEncoder());
		protFactory.registerDecoder(Journal.ID, new JournalDecoder());
		protFactory.registerEncoder(Login.ID, new LoginEncoder());
		protFactory.registerDecoder(Login.ID, new LoginDecoder());
		protFactory.registerEncoder(Logout.ID, new LogoutEncoder());
		protFactory.registerDecoder(Logout.ID, new LogoutDecoder());
		protFactory.registerEncoder(Naca.ID, new NacaEncoder());
		protFactory.registerDecoder(Naca.ID, new NacaDecoder());
		protFactory.registerEncoder(Nfs.ID, new NfsEncoder());
		protFactory.registerDecoder(Nfs.ID, new NfsDecoder());
		protFactory.registerEncoder(User.ID, new UserEncoder());
		protFactory.registerDecoder(User.ID, new UserDecoder());
		protFactory.registerEncoder(StatNfs.ID, new StatNfsEncoder());
		protFactory.registerDecoder(StatNfs.ID, new StatNfsDecoder());
		protFactory.registerEncoder(StatDoctor.ID, new StatDoctorEncoder());
		protFactory.registerDecoder(StatDoctor.ID, new StatDoctorDecoder());
		protFactory.registerEncoder(StatAnforderung.ID,
				new StatAnforderungEncoder());
		protFactory.registerDecoder(StatAnforderung.ID,
				new StatAnforderungDecoder());
		protFactory.registerEncoder(StatDienststelle.ID,
				new StatDienststelleEncoder());
		protFactory.registerDecoder(StatDienststelle.ID,
				new StatDienststelleDecoder());
		protFactory.registerEncoder(StatTransportziel.ID,
				new StatTransportzielEncoder());
		protFactory.registerDecoder(StatTransportziel.ID,
				new StatTransportzielDecoder());
		protFactory.registerEncoder(StatEinsatzart.ID,
				new StatEinsatzartEncoder());
		protFactory.registerDecoder(StatEinsatzart.ID,
				new StatEinsatzartDecoder());
		protFactory.registerEncoder(StatNaca.ID, new StatNacaEncoder());
		protFactory.registerDecoder(StatNaca.ID, new StatNacaDecoder());
		protFactory.registerEncoder(SystemMessage.ID,
				new SystemMessageEncoder());
		protFactory.registerDecoder(SystemMessage.ID,
				new SystemMessageDecoder());
		protFactory.registerEncoder(Transportziel.ID,
				new TransportzielEncoder());
		protFactory.registerDecoder(Transportziel.ID,
				new TransportzielDecoder());
		protFactory.registerEncoder(ProtokollNr.ID, new ProtokollNrEncoder());
		protFactory.registerDecoder(ProtokollNr.ID, new ProtokollNrDecoder());
	}

	// METHODS TO SEND MESSAGES
	/**
	 * Sends a request to the server to login the user.
	 * 
	 * @param login
	 *            the authentication information to use
	 */
	public void sendLoginMessage(Login login) {
		sendMessage(Login.ID, IModelActions.LOGIN, login);
	}

	/**
	 * Sends a request to the server to logout the user.
	 * 
	 * @param logout
	 *            the logut message
	 */
	public void sendLogoutMessage(Logout logout) {
		sendMessage(Logout.ID, IModelActions.LOGOUT, logout);
	}

	public void sendStatisticsMessage(String contentType, QueryFilter filter) {
		sendMessage(contentType, IModelActions.LIST, filter, null);
	}

	public void sendStatisticsMessage(String contentType) {
		sendMessage(contentType, IModelActions.LIST, null, null);
	}

	public void sendNewProtokollNr(String contentType) {
		sendMessage(contentType, IModelActions.LIST, null, null);
	}

	/**
	 * Sends a request to the server to add the object to the database.<br>
	 * To identify the type of the content a content type must be provided.<br>
	 * A example of a content type would be
	 * <code>RosterEntry.ID</cod> that would mean
	 * that the add message contains a <code>RosterEntry</code> object.
	 * 
	 * @param contentType
	 *            the type of the content
	 * @param addMessage
	 *            the object to add
	 */
	public void sendAddMessage(String contentType, AbstractMessage addMessage) {
		sendMessage(contentType, IModelActions.ADD, addMessage);
	}

	public void sendAddAllMessage(String contentType,
			List<AbstractMessage> addList) {
		sendMessage(contentType, IModelActions.ADD_ALL, null, addList);
	}

	/**
	 * Sends a request to the server to remove the object from the database. To
	 * identify the type of the content a content type must be provided.<br>
	 * A example of a content type would be
	 * <code>RosterEntry.ID</cod> that would mean
	 * that the remove request contains a <code>RosterEntry</code> object.
	 * 
	 * @param contentType
	 *            the type of the content
	 * @param removeMessage
	 *            the object to remove
	 */
	public void sendRemoveMessage(String contentType,
			AbstractMessage removeMessage) {
		sendMessage(contentType, IModelActions.REMOVE, removeMessage);
	}

	/**
	 * Sends a request to the server to update the object in the database. To
	 * identify the type of the content a content type must be provided.<br>
	 * A example of a content type would be
	 * <code>RosterEntry.ID</cod> that would mean
	 * that the update request contains a <code>RosterEntry</code> object.
	 * 
	 * @param contentType
	 *            the type of the content
	 * @param updateMessage
	 *            the object to update
	 */
	public void sendUpdateMessage(String contentType,
			AbstractMessage updateMessage) {
		sendMessage(contentType, IModelActions.UPDATE, updateMessage);
	}

	/**
	 * Sends a listing request for the given object to the server.<br>
	 * To identify the type of the listing request a content type must be
	 * provided.<br>
	 * A example of a content type would be
	 * <code>RosterEntry.ID</cod> that would mean
	 * that the lisitng request is for <code>RosterEntry</code> objects.
	 * 
	 * @param contentType
	 *            the type of the listing request
	 * @param filter
	 *            the filter for the query
	 */
	public void requestListing(String contentType, QueryFilter filter) {
		sendMessage(contentType, IModelActions.LIST, filter, null);
	}

	/**
	 * Convenience method to send the message to the server.
	 * 
	 * @param contentType
	 *            the type of the content.
	 * @param queryString
	 *            the type of the query
	 * @param queryFilter
	 *            the filter to apply
	 * @param message
	 *            the message to send
	 */
	private void sendMessage(String contentType, String queryString,
			AbstractMessage message) {
		// create a new list
		List<AbstractMessage> messageList = new ArrayList<AbstractMessage>();
		messageList.add(message);
		sendMessage(contentType, queryString, null, messageList);
	}

	/**
	 * Convenience method to send the message to the server.<br />
	 * ASSERT: The messages in the list MUST have the same type!.
	 * 
	 * @param contentType
	 *            the type of the content.
	 * @param queryString
	 *            the type of the query
	 * @param queryFilter
	 *            the filter to apply
	 * @param messageList
	 *            the messages to send
	 */
	private void sendMessage(String contentType, String queryString,
			QueryFilter queryFilter, List<AbstractMessage> messageList) {
		// check if we have a session
		if (clientSession == null) {
			log.error("Failed to send the message");
			log.error("No client session available to send the message");
			return;
		}
		// check if we have a connection
		if (clientSession.getConnection() == null) {
			log.error("Failed to send the message");
			log.error("No connection to a server available");
			return;
		}

		// set up the factory
		XMLFactory factory = new XMLFactory();
		factory.setupEncodeFactory(clientSession.getUsername(), contentType,
				queryString);
		log.info("Send: " + clientSession.getUsername() + "," + contentType
				+ "," + queryString);
		// appply filter if we have one
		if (queryFilter != null)
			factory.setFilter(queryFilter);
		// encode the message
		String xmlMessage = factory.encode(messageList);

		// replace all new lines
		xmlMessage = xmlMessage.replaceAll("\\s\\s+|\\n|\\r",
				"<![CDATA[<br/>]]>");

		// get the connection out of the session and send the message
		MyClient connection = clientSession.getConnection();
		connection.sendMessage(xmlMessage);
	}

	// LISTENER METHODS
	/**
	 * Notification that new data is available.<br>
	 * 
	 * @param ne
	 *            the triggered net event
	 */
	@Override
	public void dataReceived(NetEvent ne) {
		// set up the factory to decode
		XMLFactory xmlFactory = new XMLFactory();
		String message = ne.getMessage().replaceAll("&lt;br/&gt;", "\n");
		xmlFactory.setupDecodeFactory(message);
		// decode the message
		ArrayList<AbstractMessage> objects = xmlFactory.decode();
		// get the type of the item
		final String contentType = xmlFactory.getContentType();
		final String queryString = xmlFactory.getQueryString();
		final String userId = xmlFactory.getUserId();
		if (log.isDebugEnabled())
			log.debug("Received: " + userId + "," + contentType + ","
					+ queryString);

		// try to get a listener for this message
		ListenerFactory listenerFactory = ListenerFactory.getDefault();
		if (listenerFactory.hasListeners(contentType)) {
			IModelListener listener = listenerFactory.getListener(contentType);
			// now pass the message to the listener
			if (IModelActions.ADD.equalsIgnoreCase(queryString))
				listener.add(objects.get(0));
			if (IModelActions.ADD_ALL.endsWith(queryString))
				listener.addAll(objects);
			if (IModelActions.REMOVE.equalsIgnoreCase(queryString))
				listener.remove(objects.get(0));
			if (IModelActions.UPDATE.equalsIgnoreCase(queryString))
				listener.update(objects.get(0));
			if (IModelActions.LIST.equalsIgnoreCase(queryString))
				listener.list(objects);
			if (IModelActions.LOGIN.equalsIgnoreCase(queryString))
				listener.loginMessage(objects.get(0));
			if (IModelActions.LOGOUT.equalsIgnoreCase(queryString))
				listener.logoutMessage(objects.get(0));
			if (IModelActions.SYSTEM.equalsIgnoreCase(queryString))
				listener.systemMessage(objects.get(0));
		} else {
			log.error("No listener found for the message type: " + contentType);
			IModelListener listener = listenerFactory
					.getListener("sessionManager");
			if (objects.size() > 0)
				listener.transferFailed(contentType, queryString,
						objects.get(0));
		}
	}

	/**
	 * Notification that the status of the socket changed.<br>
	 * 
	 * @param status
	 *            the new status.
	 **/
	@Override
	public void socketStatusChanged(MyClient client, int status) {
		// reset the connection status
		if (status == IConnectionStates.STATE_DISCONNECTED) {
			// not connected
			connected = false;
			// destroy the session
			clientSession = null;
			// destory the server info
			connectionInfo = null;
		}
		// inform the listeners
		ListenerFactory listenerFactory = ListenerFactory.getDefault();
		IModelListener listener = listenerFactory.getListener("sessionManager");
		listener.connectionChange(status);
	}

	/**
	 * Norification that the transfer of the data failed.
	 * 
	 * @param ne
	 *            the triggered net event
	 */
	@Override
	public void dataTransferFailed(NetEvent ne) {
		log.warn("failed to send the data:" + ne.getMessage());
		// decode the message
		XMLFactory xmlFactory = new XMLFactory();
		xmlFactory.setupDecodeFactory(ne.getMessage());
		// inform the listeners
		ListenerFactory listenerFactory = ListenerFactory.getDefault();
		IModelListener listener = listenerFactory.getListener("sessionManager");
		ArrayList<AbstractMessage> decodedStuff = xmlFactory.decode();
		if (decodedStuff.size() > 0)
			listener.transferFailed(xmlFactory.getContentType(),
					xmlFactory.getQueryString(), decodedStuff.get(0));
	}
}
